const path = require("path");
const webpack = require("webpack");
const { merge } = require("webpack-merge");
const devConfig = require("./webpack.dev.js");
const prodConfig = require("./webpack.prod.js");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); //提取css的插件
const BundleAnalyzerPlugin =
  require("webpack-bundle-analyzer").BundleAnalyzerPlugin; //分析打包文件的插件
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin"); //压缩css的插件
const glob = require("glob-all");
const { PurgeCSSPlugin } = require("purgecss-webpack-plugin"); //清理无用css代码
const chalk = require("chalk");
const ProgressBarPlugin = require("progress-bar-webpack-plugin"); //进度条插件
const { type } = require("os");

const env = process.env.NODE_ENV;

const PATHS = {
  src: path.join(__dirname, "src"),
};

// console.log("环境变量：", env);

// console.log("PATHS.src：", PATHS.src);

// 获取处理样式的Loaders
const getStyleLoaders = (preProcessor) => {
  return [
    // MiniCssExtractPlugin.loader将CSS样式提取为单独的CSS文件，通过链接方式(link)引入到HTML中
    env === "development" ? "style-loader" : MiniCssExtractPlugin.loader,
    "css-loader",
    {
      loader: "postcss-loader",
      options: {
        // webpack4
        postcssOptions: {
          plugins: [
            // 对应的插件postcss-preset-env是帮postcss找到package.json中的browserlist里面的配置，通过配置加载指定的css兼容性样式
            "postcss-preset-env", // 能解决大多数样式兼容性问题
          ],
        },
        // webpack5
        // postcssOptions: {
        //     plugins: [
        //         require('postcss-preset-env')(),
        //     ]
        // },
      },
    },
    preProcessor && {
      loader: preProcessor,
      options: {
        sourceMap: true,
      },
    },
  ].filter(Boolean);
};

// 处理js兼容性
const babelLoaderConfig = {
  loader: "babel-loader",
  options: {
    presets: [
      [
        "@babel/preset-env",
        {
          // 按需加载
          useBuiltIns: "usage",
          // 指定core-js版本
          corejs: {
            version: 3,
          },
          // 指定兼容性做到哪个版本浏览器
          targets: {
            chrome: "60",
            firefox: "60",
            ie: "9",
            safari: "10",
            edge: "17",
          },
        },
      ],
    ],
  },
};

// 导出对象
// module.exports = merge(env === 'development' ? devConfig : prodConfig, commonConfig)

// 导出函数
module.exports = (env = {}, argv) => {
  const { production } = env;
  const commonConfig = {
    entry: "./src/index.js",
    // 手动入口分离代码
    // entry: {
    //   index: {
    //     import: "./src/index.js",
    //     dependOn: "shared",
    //   },
    //   another: {
    //     import: "./src/another.js",
    //     dependOn: "shared",
    //   },
    //   shared: "lodash",
    // },
    // optimization: {
    //   runtimeChunk: "single",
    // },
    output: {
      // 缓存
      // 当把打包后dist目录部署到server上，浏览器就能够访问此server的网站及其资源。而获取资源是比较耗费时间的，这就是为什么浏览器使用一种名为缓存的技术。
      // 可以通过命中缓存，以降低网络流量，使网站加载速度更快，然后，如果我们在部署新版本时不更改资源的文件名，浏览器可能会认为它没有被更新，就会使用它的缓存版本。
      // 所以我们需要将变动后的资源文件更改文件名，没有变动的资源（node_modules中的第三方文件）不更改包名称

      // contenthash
      // [contenthash]将根据资源内容创建出唯一hash。当资源内容发生变化时，[contenthash]也会发生变化。
      filename: "js/[name]_[chunkhash:8].js",
      path: path.resolve(__dirname, "./dist"),
      /**
         publicPath: "指定基础路径"，开发环境一般是项目的根路径 /，上线之后一般是CDN的路径。
         当项目上线时，nginx 配置:
         ```plain
         server {
              listen 80;
              server_name yourdomain.com;

              location /static/ {
                  alias /path/to/your/project/dist/;
                  try_files $uri $uri/ =404;
              }
          }
          ```
          这样，访问 http://yourdomain.com/static/js/main_xxxxxxx.js 就能正确加载到资源。
          HtmlWebpackPlugin 自动生成 HTML，引用会自动加上 publicPath：
          ```html
          <script src="/static/js/main_12345678.js"></script>
          ```
       */
      publicPath: production ? "" : "",
      clean: true, // webpack 5 自动清除上次打包结果
    },
    // 优化配置
    optimization: {
      // moduleIds: "deterministic", // 因为每个 module.id 会默认基于解析顺序增加。换言之，当解析顺序发生变化，ID 也会随之改变。防止因为本地代码的修改导致 vendor bundle moduleIds变化，导致缓存失效。
      runtimeChunk: "single",
      splitChunks: {
        chunks: "all", // 默认值 async
        minSize: 20000, // 默认值 20000
        minRemainingSize: 0, // 默认值 0
        minChunks: 1, // 默认值 1
        maxAsyncRequests: 30, // 默认值 30
        maxInitialRequests: 30, // 默认值 30
        enforceSizeThreshold: 50000, // 默认值 50000
        cacheGroups: {
          /**
           * 缓存第三方库
           * 由于像 lodash 或 react 这样的第三方库很少像本地源代码一样频繁修改，因此通常推荐将第三方库提取到单独的 vendor chunk 中。
           * 将它们单独提取并缓存到浏览器中是一个推荐的做法。这样可以减少向服务器请求资源的次数，同时还能保证客户端代码与服务器代码版本的一致性。
           *
           * 通过缓存第三方库，我们可以实现以下目标：
           *   1. 减少资源请求次数，提升加载速度。
           *   2. 保证代码版本一致性，避免因版本不一致导致的错误。
           *   3. 提高用户体验，减少不必要的网络开销。
           */
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name: "vendors",
            chunks: "all",
          },
          common: {
            test: /[\\/]src[\\/]common[\\/]/,
            name: "common",
            chunks: "all",
            minSize: 0, // 最小体积，0 表示只要引用就分割
            minChunks: 1, // 最小引用次数
            priority: 10, // 优先级，防止被其他 cacheGroup 覆盖
          },
        },
      },
      usedExports: true
    },
    // devtool: 'source-map', // 添加devtool配置
    // 配置默认后缀
    resolve: {
      extensions: [".ts", ".tsx", ".js", ".json", ".svg"],
      mainFields: ["main"],
      alias: {
        "@components": path.resolve(__dirname, "src/components"),
        "@utils": path.resolve(__dirname, "src/utils"),
      },
    },
    // 配置 loader 别名
    resolveLoader: {
      alias: {
        // "my-enforce-post-loader": path.resolve(
        //   __dirname,
        //   "./custom-loaders/my-enforce-post.js"
        // ),
      },
      /**
         * modules: 这个配置项用于指定 Webpack 查找 loader 的目录。
            1. './node_modules'：Webapck 会在 node_modules 目录下查找 loader。
            2. './loader'：Webpack 会在你项目根目录下的 loader 文件夹中查找自定义 loader。
          这段配置的作用：
            1. Webpack 首先会在 ./node_modules 目录中查找 loader。
            2. 如果在 node_modules 中没有找到对应的 loader，Webpack 会继续在 ./loader 目录中查找。
          这种配置通常用于：
            1. 自定义 loader：如果你有自定义的 loader，放在 loader 目录中，这样可以避免将其放入 node_modules。
            2. 自定义配置：覆盖 Webpack 默认的 loader 查找路径。
         */
      // 配置之后使用 loader 时，可以直接写 loader 的文件名，不用写路径
      modules: ["./node_modules", "./custom-loaders"],
    },
    /**
      使用 Cache 提升构建性能 参考链接：https://zhuanlan.zhihu.com/p/412694420
        常用配置项详解：
          1、缓存类型，支持 'memory' | 'filesystem'，需要设置 filesystem 才能开启持久缓存
          2、cache.cacheDirectory：缓存文件存放的路径，默认为 node_modules/.cache/webpack
          3、cache.buildDependencies：额外的依赖文件，当这些文件内容发生变化时，缓存会完全失效而执行完整的编译构建，通常可设置为项目配置文件，如：
              ```Javascript
              module.exports = {
                cache: {
                  buildDependencies: {
                    config: [path.join(__dirname, 'webpack.dll_config.js')],
                  },
                },
              };
              ```
          4、cache.managedPaths：受控目录，Webpack 构建时会跳过新旧代码哈希值与时间戳的对比，直接使用缓存副本，默认值为 ['./node_modules']
          5、cache.profile：是否输出缓存处理过程的详细日志，默认为 false
          6、cache.maxAge：缓存失效时间，默认值为 5184000000
     */
    cache: {
      type: "filesystem"
    },
    module: {
      rules: [
        {
          test: /\.tsx?$/,
          exclude: /node_modules/,
          use: [
            babelLoaderConfig,
            // TypeScript 编译优化
            // 在 Webpack 中使用 ts-loader 编译 TS 时，由于 ts-loader 默认在「编译前进行类型检查，因此编译时间往往比较慢」
            // 通过加上配置项 transpileOnly: true，可以在编译时忽略类型检查
            {
              loader: "ts-loader",
              options: {
                transpileOnly: true,
              },
            },
          ],
        },
        // 自定义 loader
        // {
        //   test: /\.js$/,
        //   exclude: /(node_modules|bower_components)/,
        //   use: [
        //     babelLoaderConfig,
        //     {
        //       loader: path.resolve(
        //         __dirname,
        //         "./custom-loaders/clear-console-loader.js"
        //       ),
        //     },
        //   ],
        // },
        // enforce 属性
        {
          test: /\.js$/,
          exclude: /(node_modules|bower_components)/,
          loader: "my-enforce-post",
          enforce: "post",
        },
        {
          test: /\.js$/,
          exclude: /(node_modules|bower_components)/,
          use: {
            loader: path.resolve(
              __dirname,
              "./custom-loaders/my-enforce-pre.js"
            ),
          },
          enforce: "pre",
        },
        /**
         * 样式资源处理
         */
        {
          test: /\.css$/,
          // 自定义 Loader
          // {
          //     loader: path.resolve(__dirname, './custom-loaders/my-style-loader.js'),
          //     options: {
          //         loaderName: 'custom-loader'
          //     }
          // },
          // path.resolve(__dirname, './custom-loaders/my-style-loader.js'),
          use: getStyleLoaders(),
        },
        {
          test: /\.less$/,
          use: getStyleLoaders("less-loader"),
        },
        {
          test: /\.s[ac]ss$/,
          use: getStyleLoaders("sass-loader"),
        },
        {
          test: /\.styl$/,
          use: getStyleLoaders("stylus-loader"),
        },
        {
          test: /\.(png|jpg|jpeg|gif|svg)$/,
          // webpack 4 写法，webpack 5 已弃用
          // css-loader（6.XX）不要使用file-laoder和url-loader，只能使用webpack5为我们提供的asset module type。
          // 会导致对同一张图片打包出了两张，并且引发了冲突
          // use: [
          //     {
          //         loader: 'url-loader',
          //         options: {
          //             name: '[name][hash:8].[ext]',
          //             limit: 1024 * 8
          //         }
          //     },
          //     {
          //         loader: 'file-loader',
          //         options: {
          //             name: '[name][hash:6].[ext]',
          //             // outputPath: './asset'
          //         }
          //     }
          // ],
          // webpack 5
          // “资源模块”类型
          // asset/resource： 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。
          // asset/inline： 导出一个资源的 data URI。之前通过使用 url-loader 实现。
          // asset/source： 导出资源的源代码。之前通过使用 raw-loader 实现。
          // asset：在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader，并且配置资源体积限制实现。
          type: "asset",
          parser: {
            dataUrlCondition: {
              // 将小于10k的图片资源处理成base64的作用:
              // 1. 减少HTTP请求：将小于10k的图片处理成base64后，可以直接将图片嵌入到HTML、CSS或JS文件中，避免额外的HTTP请求，从而加快页面加载速度。
              // 2. 提高页面加载速度：在页面加载时，base64格式的图片可以直接从文件中读取，而不需要到服务器请求图片资源，从而可以减少页面加载时间。
              // 3. 更加安全：base64格式的图片能直接嵌入到HTML、CSS或JS文件中，不需要通过图片的URL来加载图片资源，从而可以更加保护图片资源的安全性。
              // 4. 适用于小图片：base64格式的图片适合处理小图片，因为base64编码可以将原始图片数据增加33%左右，如果图片太大，会增加文件大小和页面加载时间。
              maxSize: 8 * 1024, // 小于8kb的图片会被base64处理
            },
          },
          generator: {
            outputPath: "asset/images", // 指定输出路径
            filename: "[name][hash:6][ext]",
            publicPath: "./asset/images/", // publicPath: "./asset/images/"：意味着打包后的资源文件将相对于HTML文件所在的目录被加载。如果你的HTML文件位于根目录，那么资源文件也将从根目录加载。
          },
        },
        // 字体文件
        // {
        //     test: /\.(otf|eot|woff2?|ttf|svg)$/i,
        //     type: "asset", // 一般会转换为 "asset/inline"
        //     generator: {
        //         filename: 'asset/fonts/[name][hash:6][ext]',
        //     }
        // },
        {
          //打包其他资源(除了css、js、html、less、jpg、png、gif)资源以外的资源)
          test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, // 处理字体图标
          exclude: /\.(css|js|html|less|jpg|png|gif)$/, // 排除css、js、html、less、jpg、png、gif资源
          loader: "file-loader",
          options: {
            name: "[hash:10].[ext]",
            outputPath: "./asset/fonts",
          },
        },
        // {
        //   test: /\.html$/,
        //   // 在安装 url-loader 和 file-loader 的基础上安装 html-loader
        //   // 处理html文件的img图片(负责引入img,从而能被url-loader进行处理)
        //   loader: "html-loader",
        // },
        {
          test: /\.(csv|tsv)$/i,
          use: ["csv-loader"],
        },
        {
          test: /\.xml$/i,
          use: ["xml-loader"],
        },
      ],
    },
    plugins: [
      // webpack 4 调用清除打包目录插件
      // new CleanWebpackPlugin(),
      //压缩css
      new CssMinimizerWebpackPlugin(),
      // PurgeCSSPlugin是一个Webpack插件，用于从Webpack打包中移除未使用的CSS。
      // 这通常用于减少最终CSS文件的大小，特别是在大型项目中，可能会有很多CSS从未被引用，从而造成不必要的加载时间。
      // 属性解析
      // paths：这个属性指定了PurgeCSS应该检查哪些文件以确定哪些CSS是被使用的。它告诉插件在哪里查找可能包含CSS类的HTML、JSX、TSX等文件。
      // glob.sync方法
      // glob.sync是一个Node.js模块，用于使用glob模式匹配文件路径。在这里，它被用来同步地获取所有匹配特定模式的文件路径。
      // ${PATHS.src}/**/* glob模式
      // ${PATHS.src}是一个变量，通常在项目的配置文件中定义，指向项目的源代码目录。
      // /**/*是一个glob模式，用于匹配${PATHS.src}目录下的所有文件和子目录。
      // { nodir: true }是一个选项，告诉glob.sync不要返回目录路径，只返回文件路径。
      // 总结
      // 这段配置告诉PurgeCSSPlugin去查找项目源代码目录（src）下的所有文件，并检查这些文件以确定哪些CSS类是实际被使用的。然后，插件会从最终的CSS文件中移除所有未被这些文件引用的CSS代码。
      new PurgeCSSPlugin({
        paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
      }),
      // 进度条
      new ProgressBarPlugin({
        format: `:msg [:bar] ${chalk.green.bold(":percent")} (:elapsed s)`,
      }),
      new BundleAnalyzerPlugin({
        analyzerMode: "static",
        reportFilename: "report.html",
        openAnalyzer: false, // 不自动打开浏览器
      }),
    ],
    mode: "production",
  };
  commonConfig.mode = production ? "production" : "development";
  return merge(commonConfig, production ? prodConfig : devConfig);
};
